<!--
 * Copyright (c) 2023 FengTaiSEC Corporation.
 * @Description:
 * @Author: 刘宇薇<liuyuwei@fengtaisec.com>
 * @Date: 2023-05-30 15:14:30
 * @LastEditTime: 2025-05-12 14:44:19
 * @LastEditors: 自己的姓名<XXXXX@fengtaisec.com>
 * @FilePath: \nw-web\src\views\simulation\scenes\manage\index.vue
 * @AmendmentRecord:
-->
<template>
  <div class="detailBox">
    <TopUtils
      :info="{
        comment: COMMENT,
        ID: ID,
        name: NAME,
      }"
      :name="state.title"
      :id="ID"
      :isCopy="isCopy"
      @generatePicture="saveScene"
      @refresh="refreshTopo"
      @saveScene="saveScene(true)"
      v-if="!isSceneCreate"
    />

    <div class="screen_content">
      <div
        class="main"
        :class="{ 'scene-fold': showLeft, 'scene-no-fold': !showLeft }"
      >
        <div
          ref="graphMain"
          class="graphMain"
          @drop="onDrop"
          @dragover.prevent
        ></div>
      </div>
      <div id="minimap"></div>
      <ContextMenu />
      <Detail
        ref="nodeDetail"
        :drawer="drawer"
        :info="nodeInfo"
        :id="ID"
        :clusId="clusterID"
        @editNode="editNodeDetail"
        @closeDialog="closeFunc"
        @refreshGraph="refreshGraph"
      ></Detail>

      <el-dialog 
        v-model="drawer" 
        title="漏洞验证" 
        width="415" 
        :modal="false" 
        :close-on-click-modal="true"
        style="position: absolute; right:0;top: -100px;"
      >
        <el-form :model="verificationForm" :rules="verificationRules" ref="verificationFormRef" label-width="100px" style="padding-top: 20px;">
          <el-form-item label="环境名称" prop="envName">
            <el-input v-model="verificationForm.envName" placeholder="请输入环境名称"></el-input>
          </el-form-item>
          <el-form-item label="IP地址" prop="ip">
            <el-input v-model="verificationForm.ip" placeholder="请输入IP地址" disabled></el-input>
          </el-form-item>
          <el-form-item label="漏洞名称" prop="vulnName">
            <el-input v-model="verificationForm.vulnName" placeholder="请输入漏洞名称" disabled></el-input>
          </el-form-item>
        </el-form>
        <template #footer>
          <div class="dialog-footer-vuln">
            <el-button @click="drawer = false">取消</el-button>
            <el-button type="primary" @click="submitVerificationForm">确认</el-button>
          </div>
        </template>
      </el-dialog>
    </div>
    <!-- 信息环境表格弹框 -->
     <el-dialog 
        v-model="open" 
        :title="selected?.name+'-漏洞验证'" 
        width="1400" 
        :modal="false" 
        :close-on-click-modal="true"
      >
       <evn-dialog :qemu_id="selected.id" :env_type_info="selected.env_type" v-if="open"></evn-dialog>
    </el-dialog>
    
    <!-- 环境信息表格 -->
    <div class="env-table-container">
      <h3 class="env-table-title">环境信息列表</h3>
      <el-table :data="envNodeList" stripe style="width: 100%" >
        <el-table-column type="index" label="序号" width="80" align="center" />
        <el-table-column prop="kvmID" label="虚拟机ID" min-width="120" />
        <el-table-column prop="name" label="名称" min-width="120" />
        <el-table-column prop="env_type_info" label="环境类型" min-width="120" />
        <el-table-column prop="node_env_id" label="环境ID" min-width="120" />
        <el-table-column prop="baseImageName" label="镜像/靶标" min-width="150" />
        <el-table-column prop="cores" label="CPU核心数" width="120" align="center" />
        <el-table-column prop="memory" label="内存(M)" width="120" align="center" />
        <el-table-column prop="storage" label="硬盘(G)" width="120" align="center" />
        <el-table-column prop="computerName" label="操作系统" min-width="120" />
        <el-table-column label="操作" width="200" align="center" fixed="right">
          <template #default="scope">
            <el-button 
              type="text" 
              :loading="scope.row.loading" 
              @click="handleOperation(scope.row, scope.row.status === 1 ? 'stop' : 'start')">
              {{ scope.row.status === 1 ? '停止' : '启动' }}
            </el-button>
            <el-divider direction="vertical"></el-divider>
            <el-button 
              type="text" 
              :disabled="scope.row.status === 1" 
              :loading="scope.row.loading" 
              @click="handleOperation(scope.row, 'restart')">
              重启
            </el-button>
            <el-button 
              type="text" 
              @click="handleOpen(scope.row)">
              详情
            </el-button>
          </template>
        </el-table-column>
      </el-table>
    </div>
  </div>
</template>
<script lang="ts" setup>
import {
  onMounted,
  onUnmounted,
  reactive,
  ref,
} from 'vue';

import eventBus from 'vue3-eventbus';
import type { Graph } from '@antv/x6';
import { useRouter } from 'vue-router';
import Detail from './children/detail.vue';
import TopUtils from './children/topUtils.vue';
import ContextMenu from './children/contextMenu.vue';
import {
  initGraph,
  initNode,
} from './service/initScene';
import {
  getToolList,
  getDescFunc,
  startVuln,
  editorPutNode,
  controlEnvVM
} from '@/api/topo.js';
import { dataURLtoFile, getRandomIP } from '@/utils/common';
import { showPorts } from './service/utils';
import { ElMessage } from 'element-plus/es';
import { emitter } from '@/utils/bus';
import {
  showContext,
  refreshGraph,
} from './service/vueService';
import { allCount } from '@/layout/notification/service/service';
import { useGraphStore } from '@/pinia/modules/graph';
import { useCommonStore } from "@/stores/common";
const { getEnvId, getEnvName, getEnvComment } = useCommonStore();
const graphStore = useGraphStore();
const router = useRouter();
const ID = ref(null);
ID.value = router.currentRoute.value.query.id || getEnvId();
const NAME = router.currentRoute.value.query.name || getEnvName();
const COMMENT = router.currentRoute.value.query.comment || getEnvComment();
let clusterID = ref(0);
let isCopy = ref('');
let sceneDetail = ref({});

const showLeft = ref(true);
const verificationFormRef = ref();
const verificationForm = reactive({
  ip: '',
  vulnName: COMMENT,
  envName: NAME,
  env_type: '',
  node_env_id: '',
});
const selected = ref();
const open = ref(false);
const handleOpen = (row) => {
  selected.value = row;
  open.value = true;
}
const verificationRules = reactive({
  ip: [
    { required: true, message: '请输入IP地址', trigger: 'blur' },
    { pattern: /^(\d{1,3}\.){3}\d{1,3}$/, message: '请输入正确的IP地址格式', trigger: 'blur' }
  ],
  vulnName: [
    { required: false, message: '请输入漏洞名称', trigger: 'blur' }
  ],
  envName: [
    { required: false, message: '请输入环境名称', trigger: 'blur' }
  ]
});

// 提交验证表单
const submitVerificationForm = async () => {
  if (!verificationFormRef.value) return;
  await verificationFormRef.value.validate(async (valid, fields) => {
    if (valid) {
      const res = await startVuln({
        name: verificationForm.envName,
        ip: verificationForm.ip,
        exploit: verificationForm.vulnName,
        id: +ID.value,
        env_type: verificationForm.env_type,
        node_env_id: verificationForm.node_env_id,
      });
      if(res.code === 200){
        ElMessage.success('验证任务创建成功');
        verificationFormRef.value.resetFields();
      }
    } else {
      console.log('验证失败', fields);
    }
  });
};
//容器控制台
const dockerDialogList = ref([]);
const dockerComponent = ref(null);
// 容器日志
const dockerLogList = ref([]);
const envNodeList = ref([]);
const NODES = ref([]);
const dockerLogComponent = ref(null);
// 主画布实例
const graphMain = ref(null);
// 保存拓扑实例
let graph: Graph = null;
// 保存左边栏拓扑节点实例
let stencil = null;
//详情框
const drawer = ref(null);
//流量详情框
const trafficDrawer = ref(null);
//工具执行
const toolExecuteDrawer = ref(null);

const state = reactive({
  // 保存 除内置和我的图形 节点信息
  image: [],
  //  设置节点分组类型
  groups: [],
  // 拓扑名称
  title: NAME,
});

// hiddenTag 避免鼠标离开时，判断连接的接口还没有完成，导致连线完成后， 程序还去隐藏之前接口返回的数据
// 导致连线后 不能连接的组件消失
const hiddenTag = ref(true);
const nodeInfo = ref({});

//虚拟机控制台
const dialogList = ref([]);
const virtualComponent = ref(null);
const nodeDetail = ref(null);
//画布粘贴
const contextGraphVisible = ref(false);
const contextOptions = ref({});


// 生成封面
const isSceneCreate = ref(false);

// 漏洞验证
const verificationEnv = arg => {
  getDescFunc(ID.value, arg.id).then(res => {
    if (res.code === 200) {
      const resp = NODES.value.find(i => i.id === arg.id)
      console.log('resp', resp)

      verificationForm.ip = res.data.instancePorts[0]?.ipV4?.split('/')[0];
      // verificationForm.qemu_id = res.data.qemuID;
      verificationForm.env_type = resp.env_type;
      verificationForm.node_env_id = resp.node_env_id;
      
      drawer.value = true;
    }
  });
  showContext(false);
};

const pasteFunc = () => {
  const clipBoard = graph.isClipboardEmpty();
  if (!clipBoard) {
    graph.paste();
  } else {
    ElMessage.warning(`无内容!`);
  }
  hideDetail();
};
const hideDetail = () => {
  contextGraphVisible.value = false;
};
const graphContext = arg => {
  contextOptions.value = {
    position: 'absolute',
    left: showLeft.value
      ? arg.e.clientX - 300 + 'px'
      : arg.e.clientX - 250 + 'px',
    top: arg.e.clientY - 190 + 'px',
    zIndex: 1000,
    width: '110px',
    // background: '#fff',
    // padding: '10px 20px',
    // boxShadow: '0px 0px 12px rgba(0, 0, 0, 0.12)',
    // border: '1px solid #e4e7ed',
    // borderRadius: '2px',
  };
  contextGraphVisible.value = true;
};
// 监听websocket消息通知
const taskFunc = data => {
  const { msg } = data;
  const info = msg.update;
  allCount.value = msg.count;
  if (info) {
    let mapList = sessionStorage.getItem('mapList');
    mapList = JSON.parse(mapList);
    if (mapList && mapList[ID.value] == info.ID) {
      if (info.status == 3) {
        drawer.value = false;
        refreshGraph(ID.value, graph);
      } else {
        delete mapList[ID.value];
        sessionStorage.setItem('mapList', JSON.stringify(mapList));
      }
    }
    let nodeList = sessionStorage.getItem('nodeList');
    nodeList = JSON.parse(nodeList);
    if (nodeList[info.ID]) {
      let options = nodeList[info.ID].options;
      if (info.status == 3) {
        if (
          options.indexOf('start') !== -1 ||
          options.indexOf('stop') !== -1 ||
          options.indexOf('reset') !== -1
        ) {
          changeNodeStatus(info.ID, nodeList[info.ID].ID, options);
        } else if (options == 'acl') {
          if (nodeDetail.value.getMyNodeList) {
            nodeDetail.value.getMyNodeList();
          }
          delete nodeList[info.ID];
          sessionStorage.setItem('nodeList', JSON.stringify(nodeList));
        }
      } else {
        delete nodeList[info.ID];
        sessionStorage.setItem('nodeList', JSON.stringify(nodeList));
      }
    }
  }
};
// 监听子组件方法刷新画布
const refreshTopo = async () => {
  await refreshGraph(ID.value, graph);
};

const setTaskId = async (taskID, id, flag) => {
  let mapList = sessionStorage.getItem('nodeList') || '{}';
  mapList = JSON.parse(mapList);
  mapList[taskID] = { ID: id, options: flag };
  sessionStorage.setItem('nodeList', JSON.stringify(mapList));
};

// 处理虚拟机操作
const handleOperation = async (row, type) => {
  row.loading = true;
  try {
    const res = await controlEnvVM({
      id: row.id,
      operation: type,
      env_type: row.env_type,

    });
    if (res.code === 200) {
      ElMessage.success(res.msg);
      //initContainer();
      row.loading = false;
    }
  } catch (error) {
    console.error(`${type}操作失败`, error);
  } finally {
    row.loading = false;
  }
};

onMounted(async () => {
  await initContainer();
  //监听增加节点/连线
  eventBus.on('addScreenNode', changeGraph);
  //监听移除节点/连线
  // eventBus.on('removeScreenNode', removeCeil);
  //监听修改节点/连线
  eventBus.on('changePutGraph', changePutGraph);
  //展开详情编辑框
  eventBus.on('editNode', editNodeFunc);
  // 监听漏洞验证
  // eventBus.on('verificationEnv', verificationEnv);
  
  
  //流量重放
  // eventBus.on('trafficReplay', trafficReplayFunc);
  // 工具执行
  // eventBus.on('toolExecute', toolExecuteFunc);
  //控制台
  // eventBus.on('changeVirtual', changeVirtual);
  // 容器日志
  // eventBus.on('containerLogs', containerLogs);
  //编辑开启状态
  // eventBus.on('openFunc', openFunc);
  //关闭详情
  // eventBus.on('hideDetail', closeFunc);
  // 监听websocket消息通知
  // emitter.on('Task', taskFunc);
  //监听右键画布
  // eventBus.on('graphContext', graphContext);
  // eventBus.on('hideDetail', hideDetail);

  // eventBus.on('changeText', changeText);
  // 监听鼠标按下边
  // eventBus.on('mouseDownEdge', mouseDownEdge);
  // 监听鼠标离开边
  // eventBus.on('mouseLeaveEdge', mouseLeaveEdge);

  sessionStorage.setItem('nodeList', '{}');
  // 监听主题管理改变
  emitter.on('changeTheme', initContainer);
});

onUnmounted(async () => {
  eventBus.off('addScreenNode', changeGraph);
  eventBus.off('editNode', editNodeFunc);
  // eventBus.off('removeScreenNode', removeCeil);
  eventBus.off('changePutGraph', changePutGraph);
  // eventBus.off('verificationEnv', verificationEnv);
  // eventBus.off('changeVirtual', changeVirtual);
  // eventBus.off('containerLogs', containerLogs);
  // eventBus.off('openFunc', openFunc);
  // eventBus.off('trafficReplay', trafficReplayFunc);
  // eventBus.off('toolExecute', toolExecuteFunc);
  // eventBus.off('hideDetail', closeFunc);
  // emitter.off('Task', taskFunc);
  // eventBus.off('graphContext', graphContext);
  // eventBus.off('hideDetail', hideDetail);
  // eventBus.off('changeText', changeText);
  // eventBus.off('mouseDownEdge', mouseDownEdge);
  // eventBus.off('mouseLeaveEdge', mouseLeaveEdge);
  // emitter.off('changeTheme', initContainer);
  showContext(false);
});

let hiddenInstances = [];
const mouseDownEdge = async arg => {
  hiddenTag.value = true;
  // 区分连线和点击连接桩，如果是连线那么target应该有cell，连接桩的话是没有cell属性的
  if (arg.view.cell.target.cell) {
    hiddenTag.value = false;
    return;
  }
  if (hiddenTag.value) {
      // 避免该接口再连线完成后触发，导致连线后隐藏组件
        hiddenInstances = arg.data;
        arg.data.forEach(item => {
          let node = graph.getCellById(item);
          node.setVisible(false);
        });
  }
  hiddenTag.value = false;
};

const mouseLeaveEdge = async arg => {
  hiddenInstances.forEach(item => {
    let node = graph.getCellById(item);
    node.setVisible(true);
  });
  hiddenTag.value = false;
};
const changeText = async arg => {
  let cell = graph.getCellById(arg.id);
  cell.attr('text/text', arg.text);
};
const editNodeDetail = async (arg, image) => {
  let cell = graph.getCellById(arg.id);
  cell.attr('text/text', arg.attrs.text.text);
  cell.attr('image/xlink:href', arg.attrs.image['xlink:href']);
  if (image) {
    cell.attr('.rankCenter/xlink:href', simulationCover(image));
  }
};
const changeNodeStatus = async (taskID, id, flag) => {
  let cell = graph.getCellById(id);
  cell.attr(
    '.rank/fill',
    flag === 'stop'
      ? '#F56C6C'
      : flag === 'start' || flag === 'restart'
      ? '#67C23A'
      : '#909399',
  );
  let nodeList = sessionStorage.getItem('nodeList');
  nodeList = JSON.parse(nodeList);
  delete nodeList[taskID];
  sessionStorage.setItem('nodeList', JSON.stringify(nodeList));
};

/**
 * 设置初始化弹窗zIndex
 * @param curData
 */
const setInitDialogZIndex = curData => {
  // 初始化控制台zIndex
  let containerArr = [
      ...dialogList.value,
      ...dockerDialogList.value,
      ...dockerLogList.value,
    ],
    currentZIndex = 10000,
    maxZIndex = 10000;
  // 获取最大的zIndex
  if (containerArr.length > 0) {
    maxZIndex = Math.max(...containerArr.map(item => item.zIndex || 0));
  }

  // 设置新的zIndex
  currentZIndex = maxZIndex + 1;
  curData.zIndex = currentZIndex;
};
// 容器日志
const containerLogs = async arg => {
  let index = dockerLogList.value.findIndex(item => {
    return item.RID == arg.id;
  });
  let nodeInfo = await getDescFunc(ID.value, arg.id);
  if (nodeInfo.data.dockerID == 0) {
    ElMessage.warning('获取容器id失败');
    return;
  }
  let dockerData = {
    id: ID.value,
    RID: arg.id,
    name: nodeInfo.data.name,
    zIndex: 10000,
    data: {
      ID: nodeInfo.data.dockerID,
    },
  };

  setInitDialogZIndex(dockerData);

  if (index == -1) {
    dockerLogList.value.push(dockerData);
  } else {
    topDockerLogDialog(dockerData);
  }
};

/**
 * @description 初始化画布
 */
const initContainer = async () => {
  if (!graphMain.value) return;
  graph = initGraph(graphMain.value);
  // 新增：左键点击节点弹出详情
  graph.on('node:click', ({ node }) => {
    editNodeFunc(node);
  });
  //初始化节点/连线
  sceneDetail = await refreshGraph(ID.value, graph);
  envNodeList.value = sceneDetail.value.env_info;
  NODES.value = sceneDetail.value.nodes;
  clusterID.value = sceneDetail.value.clusterID;
  isCopy.value = sceneDetail.value.isCopy;

  // 获取初始化边信息
  graphStore.edges = JSON.stringify(graph.getEdges());
};
/**
 * @description 拓扑图片
 */
// 生成当前拓扑信息
const saveScene = async transfer => {

  isSceneCreate.value = true;
  graph.toPNG(
    async dataUri => {
      const file = dataURLtoFile(dataUri, 'topology');
      const formData = new FormData();
      formData.append('file', file);
      const resUpload = await uploadMyGraphicsFile(formData);
      if (!(resUpload instanceof Error)) {

        if (!transfer) {
          ElMessage.success(`生成成功!`);
        }
        if (transfer === true) {
          let resDetail = await transferScene(ID.value);
          if (!(resDetail instanceof Error)) {
            ElMessage.success(`操作成功!`);
          }
        }

        isSceneCreate.value = false;
      } else {
        isSceneCreate.value = false;
      }
    },
    { quality: 1, padding: 10, width: 240, height: 240 },
  );
};
//修改topo详情
const changeGraph = async para => {
  let options =
    para.value.attrs.image && para.value.attrs.image.options
      ? para.value.attrs.image.options
      : null;

  // 第一次拖出组件并修改后复制粘贴，name不变
  let val;
  if (para.type.indexOf('node') != -1) {
    options.name = para.value.attrs.text.text;
    val = options;
  } else {
    val = para.value;
  }

  if (para.type.indexOf('node') != -1) {
    showPorts(para.value, false);
    let newObj = para.value.toJSON();
    // // 判断是否是矩形框
    // if (newObj.zIndex === -1) {
    //   newObj.shape = '';
    // }

    let arg = options
      ? {
          type: para.value.attrs.image.options.type || '',
          disk: para.value.attrs.image.options.disk || 0,
          cores: para.value.attrs.image.options.cpu || 0,
          memory: para.value.attrs.image.options.mem || 0,
          startConfig: para.value.attrs.image.options.startConfig || '',
          name: val.name || para.value.attrs.text.text || '',
          cpuModel: para.value.attrs.image.options.cpuModel || '',
          videoType: para.value.attrs.image.options.videoType || '',
          busType: para.value.attrs.image.options.busType || '',
          isTablet: para.value.attrs.image.options.isTablet || false,
          category: para.value.attrs.image.options.category || 0,
          componentCategory:
            para.value.attrs.image.options.componentCategory || '',
          id: para.value.attrs.image.options.id || 0,
          clusterNetworkID:
            val.ID || para.value.attrs.image.options.clusterNetworkID || 0,
          baseImagePath:
            val.path || para.value.attrs.image.options.baseImagePath || '',
          baseImageSystemType:
            val.systemType ||
            para.value.attrs.image.options.baseImageSystemType ||
            0,
          baseImageName: para.value.attrs.image.options.name || '',
          baseImageType:
            (val.equipment && val.equipment.type) ||
            para.value.attrs.image.options.baseImageType ||
            0,
          mirrorImage:
            val.mirror_image || para.value.attrs.image.options.mirrorImage || 0,
          baseImageUsername:
            (val.equipment && val.equipment.userName) ||
            para.value.attrs.image.options.baseImageUsername ||
            '',
          baseImagePassword:
            (val.equipment && val.equipment.passWord) ||
            para.value.attrs.image.options.baseImagePassword ||
            '',
          baseImageIpAddr:
            (val.equipment && val.equipment.ipAddr) ||
            para.value.attrs.image.options.baseImageIpAddr ||
            '',
          baseImagePort:
            (val.equipment && val.equipment.port) ||
            para.value.attrs.image.options.baseImagePort ||
            '',
          instanceData: {
            id: newObj.id,
            portInfo: newObj.ports.items,
            height: newObj.size.height,
            width: newObj.size.width,
            x: newObj.position.x,
            y: newObj.position.y,
            zIndex: newObj.zIndex,
            shape: newObj.shape,
          },
          toolData: para.value.attrs.image.options.config,
          toolUUID: para.value.attrs.image.options.uuid,
          image: para.value.attrs.image
            ? para.value.attrs.image['xlink:href'] === '/'
              ? '1'
              : para.value.attrs.image['xlink:href']
            : '',
        }
      : {};

    let cell = graph.getCellById(newObj.id);
    cell.attr(
      '.rankCenter/xlink:href',
      simulationCover(res.data.computerImage),
    );
      if (
        para.value.attrs.image.options.category === 7 ||
        para.value.attrs.image.options.category === 8
      ) {
        if (res.data.isRunning) {
          cell.attr('.rank/fill', '#67C23A');
        } else {
          cell.attr('.rank/fill', '#F56C6C');
        }
      }
  } else {
    let para1 = {
      id: val.id,
      source: val.source,
      target: val.target,
      zIndex: val.zIndex,
    };
    let arr = [
      {
        markup: [
          {
            tagName: 'ellipse',
            selector: 'labelBody',
          },
          {
            tagName: 'text',
            selector: 'labelText',
          },
        ],
        attrs: {
          labelText: {
            text: '',
            fill: '#474747',
            textAnchor: 'middle',
            textVerticalAnchor: 'middle',
          },
          labelBody: {
            ref: 'labelText',
            refRx: '60%',
            refRy: '60%',
            stroke: '#5F95FF', // 第一个
            fill: '#fff',
            strokeWidth: 1,
          },
        },
        position: { distance: 0.1 },
      },
      {
        markup: [
          {
            tagName: 'ellipse',
            selector: 'labelBody',
          },
          {
            tagName: 'text',
            selector: 'labelText',
          },
        ],
        attrs: {
          labelText: {
            text: '',
            fill: '#474747',
            textAnchor: 'middle',
            textVerticalAnchor: 'middle',
          },
          labelBody: {
            ref: 'labelText',
            refRx: '60%',
            refRy: '60%',
            stroke: '#5F95FF', // 第二个
            fill: '#fff',
            strokeWidth: 1,
          },
        },
        position: { distance: 0.9 },
      },
    ];
    let sourceCell = graph.getCellById(para1.source.cell);
    let targetCell = graph.getCellById(para1.target.cell);
    if (
      sourceCell.attrs.image.options.category == 7 ||
      targetCell.attrs.image.options.category == 7
    ) {
      const materialID =
        sourceCell.attrs.image.options.category == 7
          ? para1.source.cell
          : para1.target.cell;
      let nodeInfo = await getDescFunc(ID.value, materialID);
      verificationForm.ip = nodeInfo.data.ip;
      materialInterfaceData.value = {
        sourceCell,
        targetCell,
        para1,
        para,
      };

      if (!(nodeInfo instanceof Error)) {
        if (nodeInfo.data.localMaterial?.linkEquipment?.type === 1) {
          choiceMaterialType.value = 1;
          materialInterfaceList.value = nodeInfo.data.localMaterial.interfaces;
        } else if (nodeInfo.data.localMaterial?.linkEquipment?.type === 2) {
          choiceMaterialType.value = 2;
          materialInterfaceList.value = nodeInfo.data.localMaterial.interfaces;
          materialEqInterfaceList.value =
            nodeInfo.data.localMaterial.linkEquipment.interfaces;
          await getMaterialInterface(sourceCell.id, targetCell.id);
        } else {
          ElMessage.warning('所连物理设备的接入设备信息获取失败');
          unmatchMaterialInterface();
          return;
        }
      }
      materialInterfaceShow.value = true;
      return;
    }
    graph?.removeCell(para.value, { slient: true });
      // 极特殊的情况下，无法自动触发鼠标离开事件，导致无法显示被隐藏的组件，这里，确保连线成功后手动触发一次
      hiddenTag.value = false;
      await mouseLeaveEdge(null);
  }
}

//物理设备连接弹窗
const materialInterfaceShow = ref(false);
const materialInterfaceID = ref(null);
const materialInterfaceConVirIP = ref(null);
const materialInterfaceConVirWg = ref(null);
const materialInterfaceData = ref({});
const materialInterfaceList = ref([]);
const materialEqInterfaceList = ref([]);
const choiceMaterialType = ref(0);

const changeMaterialInterface = async item => {
  if (choiceMaterialType.value == 1) {
    materialInterfaceConVirIP.value = '';
    // 计算链路设备连接类型IP
    for (let i = 0; i < materialInterfaceList.value.length; i++) {
      if (item === materialInterfaceList.value[i].ID) {
        const _ip = getRandomIP(materialInterfaceList.value[i].host);
        materialInterfaceConVirIP.value = _ip.randomIp + '/' + _ip.mask;
      }
    }
    return;
  }
};

const getMaterialInterface = async () => {
  // 计算路由设备连接类型IP
  const _ip = getRandomIP(res.data?.host);
  materialInterfaceConVirIP.value = _ip.randomIp + '/' + _ip.mask;
  materialInterfaceConVirWg.value = '';
};

const matchMaterialInterface = async () => {
  let { sourceCell, targetCell, para1, para } = materialInterfaceData.value;
  let arr = [
    {
      markup: [
        {
          tagName: 'ellipse',
          selector: 'labelBody',
        },
        {
          tagName: 'text',
          selector: 'labelText',
        },
      ],
      attrs: {
        labelText: {
          text: '',
          fill: '#474747',
          textAnchor: 'middle',
          textVerticalAnchor: 'middle',
        },
        labelBody: {
          ref: 'labelText',
          refRx: '60%',
          refRy: '60%',
          stroke: '#5F95FF', // 第一个
          fill: '#fff',
          strokeWidth: 1,
        },
      },
      position: { distance: 0.1 },
    },
    {
      markup: [
        {
          tagName: 'ellipse',
          selector: 'labelBody',
        },
        {
          tagName: 'text',
          selector: 'labelText',
        },
      ],
      attrs: {
        labelText: {
          text: '',
          fill: '#474747',
          textAnchor: 'middle',
          textVerticalAnchor: 'middle',
        },
        labelBody: {
          ref: 'labelText',
          refRx: '60%',
          refRy: '60%',
          stroke: '#5F95FF', // 第二个
          fill: '#fff',
          strokeWidth: 1,
        },
      },
      position: { distance: 0.9 },
    },
  ];
  para1 = Object.assign(para1, {
    materialInterfaceID: materialInterfaceID.value,
    materialInstanceIP: materialInterfaceConVirIP.value,
    materialInstanceGW: materialInterfaceConVirWg.value,
  });

  // 极特殊的情况下，无法自动触发鼠标离开事件，导致无法显示被隐藏的组件，这里，确保连线成功后手动触发一次
  materialInterfaceShow.value = false;
  materialInterfaceID.value = null;
  materialInterfaceData.value = {};
  materialInterfaceList.value = [];
  materialEqInterfaceList.value = [];
  materialInterfaceConVirWg.value = null;
  materialInterfaceConVirIP.value = null;
  hiddenTag.value = false;
  await mouseLeaveEdge(null);
};
const unmatchMaterialInterface = () => {
  let { para } = materialInterfaceData.value;
  graph?.removeCell(para.value, { slient: true });
  materialInterfaceShow.value = false;
  materialInterfaceID.value = null;
  materialInterfaceData.value = {};
  materialInterfaceList.value = [];
  materialEqInterfaceList.value = [];
  materialInterfaceConVirWg.value = null;
  materialInterfaceConVirIP.value = null;
  choiceMaterialType.value = ref(0);
};
//修改节点/连线
const changePutGraph = async para => {
  let jsonData = para;
  try {
    jsonData = jsonData.value.toJSON();
  } catch {
    jsonData = null;
    return;
  }

  if (para.type.indexOf('edge') != -1) {
    let arg = {
      id: jsonData.id || '',
      source: jsonData.source,
      target: jsonData.target,
      router: jsonData.router ? jsonData.router['name'] : '',
      strokeDasharray: jsonData.attrs.line.strokeDasharray,
      connector: jsonData.connector ? jsonData.connector['name'] : '',
      zIndex: jsonData.zIndex,
    };
    graphStore.edges = JSON.stringify(graph.getEdges());
      // 判断
      JSON.parse(graphStore.edges).forEach(item => {
        if (item.id == jsonData.id) {
          graph?.addEdge(item, { slient: true });
          item = jsonData;
        }
      });
  } else {
    let arg = {
      id: ID.value,
      node_id: jsonData.id || '',
      height: jsonData.size.height,
      width: jsonData.size.width,
      x: jsonData.position.x,
      y: jsonData.position.y,
      zIndex: jsonData.zIndex,
    };
    let res = await editorPutNode(arg);
    if (!(res instanceof Error)) {
    } else {
      const flg = graph.canUndo();
      if (flg) {
        graph.undo();
      }
    }
  }
};

//展开详情编辑框
const editNodeFunc = async arg => {
  nodeInfo.value = arg.toJSON();
  nodeInfo.value['id'] = arg.id;
  verificationEnv(arg)
};

//展开流量重放选择框
const trafficReplayFunc = async arg => {
  nodeInfo.value = arg.toJSON();
  nodeInfo.value['id'] = arg.id;
  trafficDrawer.value = true;
};
const closeTrafficFunc = () => {
  trafficDrawer.value = false;
};

//展开工具执行框
const toolExecuteFunc = async arg => {
  emitter.emit('debugTool', { id: ID.value, instanceID: arg.id });
};
const closeToolExecuteFunc = () => {
  trafficDrawer.value = false;
};
const closeFunc = () => {
  drawer.value = false;
};

/**
 * 设置拖动dialog层级
 * @param item
 */
const setDragDialogZIndex = (item, DOM) => {
  let containerArr = [
    ...dialogList.value,
    ...dockerDialogList.value,
    ...dockerLogList.value,
  ];

  // 获取最大的zIndex
  let maxZIndex = Math.max(...containerArr.map(item => item.zIndex || 0));

  // 设置新的zIndex
  if (maxZIndex !== item.zIndex) {
    item.zIndex = maxZIndex + 1;
  }

  DOM.focusFunc ? DOM.focusFunc(item) : '';
};

const topDialog = item => setDragDialogZIndex(item, virtualComponent.value);

const topDockerDialog = item =>
  setDragDialogZIndex(item, dockerComponent.value);

const topDockerLogDialog = item =>
  setDragDialogZIndex(item, dockerLogComponent.value);

/**
 * 关闭虚拟机控制台
 * @param arg
 */
const closeVirtualFunc = arg => {
  let index = dialogList.value.findIndex(item => {
    return item.RID == arg.RID;
  });
  if (index !== -1) {
    dialogList.value.splice(index, 1);
  }
};

/**
 * 关闭docker控制台
 * @param arg
 */
const closeDockerFunc = arg => {
  let index = dockerDialogList.value.findIndex(item => {
    return item.RID == arg.RID;
  });
  if (index !== -1) {
    dockerDialogList.value.splice(index, 1);
  }
};

/**
 * 关闭日志控制台
 * @param arg
 */
const closeDockerLogFunc = arg => {
  let index = dockerLogList.value.findIndex(item => {
    return item.RID == arg.RID;
  });
  if (index !== -1) {
    dockerLogList.value.splice(index, 1);
  }
};

// ================== 自定义拖拽 begin ==================
const currentDragItem = ref(null);
function onDragStart(item) {
  currentDragItem.value = item;
}

function onDrop(e) {
  e.preventDefault();
  if (!currentDragItem.value) return;
  // 获取画布坐标
  const { x, y } = graph.clientToLocal(e.clientX, e.clientY);
  // 用initNode生成节点数据
  let nodeArr = [];

  if (!currentDragItem.value?.options?.attrs) {
    nodeArr = initNode([{ name: '自定义', fileList: [currentDragItem.value] }]);
  } else {
    nodeArr = [{ groupName: '自定义', list: [currentDragItem.value.options] }];
  }

  if (nodeArr.length && nodeArr[0].list.length) {
    const nodeData = nodeArr[0].list[0];
    nodeData.x = x;
    nodeData.y = y;
    graph.addNode(nodeData);
  }
  currentDragItem.value = null;
}


</script>
<style lang="scss">
.detailBox {
  height: calc(100vh - 84px);
}
.screen_content {
  display: flex;
  align-items: flex-start;
  justify-content: flex-start;
  height: calc(100% - 290px);
  position: relative;
  .scenes_left {
    background: #ffffff !important;
  }
  .screen_content-component {
    height: auto;
    margin-bottom: 12px;
  }

  .screen_content-bottom {
    height: calc(100% - 140px);
    position: relative;
  }

  .nodeMenu {
    color: red;
    padding: 10px;
    background: #094585;
    border-radius: 4px;
    box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.16);
    li:hover {
      background-color: #0073f3;
      color: #ffffff;
      cursor: pointer;
    }

    li {
      padding: 5px 20px;
      border-radius: 4px;
      display: flex;
      justify-content: center;
      align-items: center;
      font-size: 11px;

      span {
        margin-left: 5px;
      }
    }
  }

  .x6-graph-scroller {
    z-index: 100;
  }

  #minimap {
    position: absolute;
    z-index: 100;
    right: 0;
    bottom: 40px;
  }

  .el-drawer__header {
    margin-bottom: 0px;
  }

  .tabContainer {
    width: 100%;
    height: 100%;
    padding: 10px;
    box-sizing: border-box;
  }

  .el-radio-button {
    border-radius: 5px;
    margin: 2px;
  }

  .el-tabs,
  .el-tab-pane {
    height: 100%;
  }

  .el-tabs__content {
    height: calc(100% - 50px) !important;
  }
  .left {
    height: 100%;
    transition: left 0.2s ease;
    position: absolute;
    box-sizing: border-box;
    background-color: #062854;
    border-radius: 5px;
    z-index: 100;
    width: 400px;
    padding: 10px;

    .leftMain {
      width: 100%;
      height: 100%;
    }
  }

  .leftIcon {
    border-radius: 50%;
    transition: width 0.2s ease;
    cursor: pointer;
    position: absolute;
    left: 390px;
    top: 50%;
    transform: translateY(-50%);
    z-index: 999;

    .lefticonimg {
      width: 10px;
      height: 75px;
      background-image: url(@/assets/topo/right.png);
      background-size: 100% 100%;
    }

    .righticonimg {
      width: 10px;
      height: 75px;
      background-image: url(@/assets/topo/left.png);
      background-size: 100% 100%;
    }

    &:hover {
      transform: scale(1.2);
    }

    &:active {
      transform: rotate(180deg);
    }
  }

  .rightIcon {
    left: -5px;
  }

  .main {
    flex: 1;
    height: 100%;
    transition: margin 0.2s ease;
    // width: calc(100% - 445px) !important;

    .graphMain {
      width: 100% !important;
      height: 100%!important;
      margin-right: 20px;
    }

    .graphMain tspan {
      user-select: none;
    }
  }
}

// 环境信息表格样式
.env-table-container {
  padding: 20px;
  border-radius: 8px;
  background-color: var(--card-bg);
  margin-top: 20px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
  
  .env-table-title {
    font-size: 16px;
    font-weight: 500;
    color: #303133;
    margin-bottom: 20px;
    padding-bottom: 10px;
    border-bottom: 1px solid #EBEEF5;
  }
  
  .el-button.is-text {
    color: #1890ff;
    
    &:hover {
      color: #66b1ff;
    }
    
    &:disabled {
      color: #C0C4CC;
    }
  }
  
  .el-table {
    .el-button + .el-divider--vertical {
      margin: 0 8px;
    }
  }
}

.flowMain {
  background-color: white;
  display: flex;
  border-radius: 4px;
  margin-top: 10px;
  height: calc(100vh - 230px);
}

.design-left-container {
  width: 400px;
  align-items: center;
  display: inline-flex;
  flex-wrap: wrap;

  .design-left-container-item {
    flex-basis: calc((100% - 46px) / 4);
    font-size: 14px;
    height: 32px;
    line-height: 32px;
    text-align: center;
    border-radius: 4px;
    margin: 2px;
    cursor: pointer;
    color: #fff;
    background-color: #093875;
    border: 1px solid #0073f3;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .design-active {
    background-color: #0073f3;
    box-shadow: none;
    border: 1px solid #0073f3;
  }
}

.scene-fold {
  // margin-left: 405px;
}

.scene-no-fold {
  //margin-left: 15px;
}

.scene-fold-left {
  left: -410px;
}

.resource-pane {
  border-radius: 12px;
  padding: 0;
  min-height: 100%;
  box-shadow: 0 2px 12px rgba(24, 144, 255, 0.04);
}
.group-bar {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 10px;
  margin-bottom: 12px;
  .group-btn {
    height: 34px;
    line-height: 34px;
    text-align: center;
    border-radius: 8px;
    background: #fff;
    color: #5b6775;
    font-size: 14px;
    cursor: pointer;
    border: 1.5px solid #e0e6ed;
    transition: all 0.18s cubic-bezier(0.4, 0, 0.2, 1);
    user-select: none;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 500;
    box-shadow: none;
    padding: 0 5px;
    &:hover {
      border-color: #1890ff;
      color: #fff;
      background: #1890ff;
      box-shadow: 0 2px 8px rgba(24, 144, 255, 0.1);
    }
    &.active {
      background: #1890ff;
      color: #fff;
      border-color: #1890ff;
      font-weight: 600;
      box-shadow: 0 4px 16px rgba(24, 144, 255, 0.16);
    }
  }
}
.search-card {
  background: #fff;
  border-radius: 28px;
  box-shadow: 0 1px 8px rgba(24, 144, 255, 0.06);
  margin-bottom: 12px;
  border: none;
  line-height: 40px;
  .compact-search-input {
    .el-input__wrapper {
      border-radius: 28px;
      border: 2px solid #e0e6ed;
      box-shadow: none;
      transition: border-color 0.2s, box-shadow 0.2s;
      padding-left: 8px;
      min-height: 36px;
      display: flex;
      align-items: center;
    }
    .el-input__prefix {
      color: #1890ff;
      margin-right: 4px;
      display: flex;
      align-items: center;
      font-size: 16px;
      svg {
        width: 16px;
        height: 16px;
      }
    }
    input {
      background: transparent;
      border-radius: 28px;
      color: #5b6775;
      font-size: 14px;
      padding-left: 0;
      &::placeholder {
        color: #c2cbe0;
        font-size: 14px;
        letter-spacing: 0.2px;
      }
    }
    &.is-focus .el-input__wrapper {
      border-color: #1890ff;
      box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.08);
      background: #fff;
    }
  }
}
.resource-collapse {
  background: transparent;
  border-top: none;
  border-bottom: none;
  height: calc(100vh - 480px);
  overflow-y: auto;
  .custom-collapse-item {
    margin-bottom: 18px;
    background: #f7faff;
    border-radius: 14px;
    box-shadow: 0 2px 12px rgba(24, 144, 255, 0.06);
    border: 1.5px solid #e0e6ed;
    overflow: hidden;
    .el-collapse-item__header {
      font-weight: 600;
      font-size: 15px;
      color: #5b6775;
      border-left: 3px solid #1890ff;
      background: #f7faff;
      border-radius: 14px 14px 0 0;
      min-height: 40px;
      align-items: center;
      display: flex;
      padding-left: 14px;
      justify-content: space-between;
      .el-collapse-item__arrow {
        margin-left: auto !important;
        order: 2;
      }
    }
    .el-collapse-item__wrap {
      background: #fff;
      border-radius: 0 0 14px 14px;
    }
    .el-collapse-item__content {
      padding: 0 10px 10px;
    }
  }
}

.resource-collapse-other {
  height: calc(100vh - 350px);
}

.resource-list {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  padding: 14px 0 6px 0;
}

.resource-card {
  width: 100%;
  min-width: 0;
  height: 88px;
  background: #fff;
  border-radius: 12px;
  box-shadow: 0 2px 8px rgba(24, 144, 255, 0.06);
  cursor: grab;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  transition: box-shadow 0.2s, border 0.2s;
  border: 1.5px solid #e0e6ed;
  &:hover {
    border-color: #1890ff;
    //background: #f0f8ff;
    box-shadow: 0 4px 16px rgba(24, 144, 255, 0.12);
  }

  .el-tooltip__trigger {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
  }

  .icon-bg {
    width: 45px;
    height: 54px;
    background: #fff;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 1px 4px rgba(24, 144, 255, 0.04);
    //border: 1.5px solid #c9d5e2;
  }
  .resource-icon {
    width: 45px;
    height: 54px;
    object-fit: contain;
  }
  .resource-name {
    display: inline-block;
    width: 68px;
    font-size: 14px;
    color: #5b6775;
    text-align: center;
    word-break: break-all;
    line-height: 1;
    max-width: 120px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 500;
    position: relative;
  }
}

// 响应式兼容
@media (max-width: 1400px) {
  .group-bar {
    grid-template-columns: repeat(4, 1fr);
  }
}
@media (max-width: 900px) {
  .group-bar {
    grid-template-columns: repeat(4, 1fr);
  }
  .resource-card {
    //height: 56px;

    .resource-name {
      font-size: 12px;
      max-width: 60px;
    }
  }
}
.dialog-footer-vuln {
  display: flex;
  justify-content: center;
  align-items: center;
  .el-button {
    width: 100px;
  }
}
</style>
